package com.lkd.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Strings;
import com.lkd.common.VMSystem;
import com.lkd.config.TopicConfig;
import com.lkd.contract.SupplyChannel;
import com.lkd.contract.SupplyContract;
import com.lkd.contract.TaskCompleteContract;
import com.lkd.dao.TaskDao;
import com.lkd.emq.MqttProducer;
import com.lkd.entity.TaskDetailsEntity;
import com.lkd.entity.TaskEntity;
import com.lkd.entity.TaskStatusTypeEntity;
import com.lkd.exception.LogicException;
import com.lkd.feign.UserService;
import com.lkd.feign.VMService;
import com.lkd.http.vo.TaskDetailsViewModel;
import com.lkd.http.vo.TaskViewModel;
import com.lkd.service.TaskDetailsService;
import com.lkd.service.TaskService;
import com.lkd.service.TaskStatusTypeService;
import com.lkd.vo.Pager;
import com.lkd.vo.UserVO;
import com.lkd.vo.VmVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Slf4j
public class TaskServiceImpl extends ServiceImpl<TaskDao, TaskEntity> implements TaskService {

    @Autowired
    private TaskStatusTypeService statusTypeService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private VMService vmService;

    @Autowired
    private UserService userService;

    @Autowired
    private TaskDetailsService taskDetailsService;

    @Autowired
    private MqttProducer mqttProducer;


    @Override
    public Pager<TaskEntity> search(Long pageIndex, Long pageSize, String innerCode, Integer userId, String taskCode, Integer status, Boolean isRepair, String start, String end) {
        Page<TaskEntity> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<TaskEntity> qw = new LambdaQueryWrapper<>();
        if (!Strings.isNullOrEmpty(innerCode)) {
            qw.eq(TaskEntity::getInnerCode, innerCode);
        }
        if (userId != null && userId > 0) {
            qw.eq(TaskEntity::getUserId, userId);
        }
        if (!Strings.isNullOrEmpty(taskCode)) {
            qw.like(TaskEntity::getTaskCode, taskCode);
        }
        if (status != null && status > 0) {
            qw.eq(TaskEntity::getTaskStatus, status);
        }
        if (isRepair != null) {
            if (isRepair) {
                qw.ne(TaskEntity::getProductTypeId, VMSystem.TASK_TYPE_SUPPLY);
            } else {
                qw.eq(TaskEntity::getProductTypeId, VMSystem.TASK_TYPE_SUPPLY);
            }
        }
        if (!Strings.isNullOrEmpty(start) && !Strings.isNullOrEmpty(end)) {
            qw
                    .ge(TaskEntity::getCreateTime, LocalDate.parse(start, DateTimeFormatter.ISO_LOCAL_DATE))
                    .le(TaskEntity::getCreateTime, LocalDate.parse(end, DateTimeFormatter.ISO_LOCAL_DATE));
        }
        //根据最后更新时间倒序排序
        qw.orderByDesc(TaskEntity::getUpdateTime);

        return Pager.build(this.page(page, qw));
    }


    @Override
    public List<TaskStatusTypeEntity> getAllStatus() {
        QueryWrapper<TaskStatusTypeEntity> qw = new QueryWrapper<>();
        qw.lambda()
                .ge(TaskStatusTypeEntity::getStatusId, VMSystem.TASK_STATUS_CREATE);

        return statusTypeService.list(qw);
    }


    /**
     * 新增运维/运营工单
     */
    @Override
    @Transactional
    public Boolean createTask(TaskViewModel taskViewModel) {

        //校验同一台机器工单是否已经存在一个
        boolean existence = hasTask(taskViewModel.getInnerCode(), taskViewModel.getProductType());
        //存在，返回true
        if (existence) {
            throw new LogicException("该机器正在执行其他工单");
        }

        //查询售货机地址
        VmVO vmInfo = vmService.getVMInfo(taskViewModel.getInnerCode());
        if (vmInfo == null) {
            throw new LogicException("没有该机器");
        }

        //校验数据
        checkVM(vmInfo.getStatus(), taskViewModel.getProductType());


        //查询执行人详细信息
        UserVO user = userService.getUser(taskViewModel.getUserId());
        if (user == null) {
            throw new LogicException("没有该员工");
        }

        //获取工单编号
        String taskCode = this.generateTaskCode();

        //设置Task(工单)值
        TaskEntity taskEntity = new TaskEntity();
        BeanUtils.copyProperties(taskViewModel, taskCode);
        //设置taskViewModel在TaskEntry中没有的值
        taskEntity.setTaskCode(taskCode);
        taskEntity.setTaskStatus(VMSystem.TASK_STATUS_CREATE);
        taskEntity.setProductTypeId(taskViewModel.getProductType());

        taskEntity.setAddr(vmInfo.getNodeAddr());
        taskEntity.setRegionId(vmInfo.getRegionId());
        taskEntity.setUserName(user.getUserName());

        save(taskEntity);

        //如果是补货工单
        if (taskEntity.getProductTypeId() == VMSystem.TASK_TYPE_SUPPLY) {

            List<TaskDetailsViewModel> details = taskViewModel.getDetails();
            for (TaskDetailsViewModel detail : details) {
                TaskDetailsEntity taskDetailsEntity = new TaskDetailsEntity();
                BeanUtils.copyProperties(detail, taskDetailsEntity);
                taskDetailsEntity.setTaskId(taskEntity.getTaskId());
                taskDetailsService.save(taskDetailsEntity);
            }

        }

            return true;
        }

        /**
         * 校验同一台机器工单是否已经存在一个
         */
        private boolean hasTask (String innerCode,int productType){
            LambdaQueryWrapper<TaskEntity> lqw = new LambdaQueryWrapper();
            lqw.select(TaskEntity::getInnerCode).eq(TaskEntity::getInnerCode, innerCode);
            lqw.eq(TaskEntity::getProductTypeId, productType);
            lqw.le(TaskEntity::getTaskStatus, VMSystem.TASK_STATUS_PROGRESS);
            //return this.count(lqw)>0;
            TaskEntity one = getOne(lqw);
            Boolean falge = false;

            if (one != null) falge = true;

            return falge;
        }

        /**
         * 生成工单编号
         *
         * @return
         */
        private String generateTaskCode () {
            //日期+序号
            String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));  //日期字符串
            String key = "lkd.task.code." + date; //redis key
            Object obj = redisTemplate.opsForValue().get(key);
            if (obj == null) {
                redisTemplate.opsForValue().set(key, 1L, Duration.ofDays(1));
                return date + "0001";
            }
            return date + Strings.padStart(redisTemplate.opsForValue().increment(key, 1).toString(), 4, '0');
        }

        /**
         * 数据校验
         *
         * @param status      售货机状态，0:未投放;1-运营;3-撤机
         * @param productType 工单类型
         */
        public void checkVM ( int status, Integer productType){

            if (productType == null) {
                throw new LogicException("订单状态为空");
            }
            //如果是投放工单，机器状态不能是运营
            if (productType == VMSystem.TASK_TYPE_DEPLOY && status == VMSystem.VM_STATUS_RUNNING) {
                throw new LogicException("机器状态是运营状态");
            }

            //如果是补货工单，状态必须为运营
            if (productType == VMSystem.TASK_TYPE_SUPPLY && status != VMSystem.VM_STATUS_RUNNING) {
                throw new LogicException("机器状态不是运营状态");
            }

            //如果是撤机工单，状态必须为运营
            if (productType == VMSystem.TASK_TYPE_REVOKE && status != VMSystem.VM_STATUS_RUNNING) {
                throw new LogicException("机器状态不是运营状态");
            }
        }


        /**
         * 接受工单
         *
         * @param id
         * @return
         */
        @Override
        public Boolean accept (Long id){

            TaskEntity taskEntity = getById(id);
            if (taskEntity.getTaskStatus() != VMSystem.TASK_STATUS_CREATE) {
                throw new LogicException("该工单不是“待处理”");
            }

            taskEntity.setTaskStatus(VMSystem.TASK_STATUS_PROGRESS);

            //接受工单，员工对应数量加一
            updateTaskZSet(taskEntity, 1);

            return this.updateById(taskEntity);

        }

        /**
         * 完成工单
         * @param id
         * @return
         */
        @Override
        public Boolean completeTask (Long id){
            TaskEntity taskEntity = this.getById(id);
            if (taskEntity.getTaskStatus() == VMSystem.TASK_STATUS_FINISH || taskEntity.getTaskStatus() == VMSystem.TASK_STATUS_CANCEL) {
                throw new LogicException("工单已经结束");
            }
            taskEntity.setTaskStatus(VMSystem.TASK_STATUS_FINISH);//修改工单状态
            this.updateById(taskEntity);

            //如果是投放工单或撤机工单
            if (taskEntity.getProductTypeId() == VMSystem.TASK_TYPE_DEPLOY || taskEntity.getProductTypeId() == VMSystem.TASK_TYPE_REVOKE) {
                noticeVMServiceStatus(taskEntity);//运维工单的封装与发送
            }
            //如果是补货工单
            if (taskEntity.getProductTypeId() == VMSystem.TASK_TYPE_SUPPLY) {
                noticeVMServiceSupply(taskEntity);//补货协议封装与下发
            }

            //完成工单，员工对应数量减一
            //updateTaskZSet(taskEntity,-1);

            return null;
        }

        /**
         * 补货协议封装与下发
         *
         * @param taskEntity
         */
        private void noticeVMServiceSupply (TaskEntity taskEntity){
            LambdaQueryWrapper<TaskDetailsEntity> lqw = new LambdaQueryWrapper<>();
            lqw.eq(TaskDetailsEntity::getTaskId, taskEntity.getTaskId());
            //查询出所有工单对应的工单详情
            List<TaskDetailsEntity> list = taskDetailsService.list(lqw);
            //构建发送内容
            SupplyContract supplyContract = new SupplyContract();

            //设置售货机id
            supplyContract.setInnerCode(taskEntity.getInnerCode());

            List<SupplyChannel> supplyChannels = new ArrayList<>();
            for (TaskDetailsEntity taskDetailsEntity : list) {
                SupplyChannel supplyChannel = new SupplyChannel();
                supplyChannel.setChannelId(taskDetailsEntity.getChannelCode());
                supplyChannel.setCapacity(taskDetailsEntity.getExpectCapacity());
                supplyChannels.add(supplyChannel);
            }
            supplyContract.setSupplyData(supplyChannels);

            //3.下发补货协议
            //发送到emq
            try {
                //完成工单，修改售货机状态
                mqttProducer.send(TopicConfig.VMS_SUPPLY_TOPIC, 2, supplyContract);
            } catch (Exception e) {
                log.error("发送工单完成协议出错错误工单编号：{}", taskEntity.getTaskId());
                throw new LogicException("发送工单完成协议出错");
            }
        }

        //运维工单的封装与发送
        private void noticeVMServiceStatus (TaskEntity taskEntity){
            TaskCompleteContract taskCompleteContract = new TaskCompleteContract();
            taskCompleteContract.setTaskType(taskEntity.getProductTypeId());
            taskCompleteContract.setInnerCode(taskEntity.getInnerCode());

            try {
                //完成工单，修改售货机状态
                mqttProducer.send(TopicConfig.VMS_COMPLETED_TOPIC, 2, taskCompleteContract);
            } catch (JsonProcessingException e) {
                log.error("运维工单发送出错，错误工单编号：{}", taskEntity.getTaskId());
                throw new LogicException("发送工单完成协议出错");
            }

        }


        /**
         * 取消工单
         *
         * @return
         */
        @Override
        public Boolean cancelTask (TaskEntity taskEntity){

            TaskEntity byId = getById(taskEntity.getTaskId());

            if (byId.getTaskStatus().equals(VMSystem.TASK_STATUS_FINISH) || byId.getTaskStatus().equals(VMSystem.TASK_STATUS_CANCEL)) {
                throw new LogicException("工单已经结束");
            }

            byId.setTaskStatus(VMSystem.TASK_STATUS_CANCEL);
            byId.setDesc(taskEntity.getDesc());

            //员工对应工单数量减一
            updateTaskZSet(byId, -1);

            return this.updateById(byId);

        }

        /**
         * 修改员工工单数量
         * @param taskEntity
         * @param score 增加的分值
         */
        private void updateTaskZSet (TaskEntity taskEntity,int score){

            VmVO vmInfo = vmService.getVMInfo(taskEntity.getInnerCode());
            if (vmInfo == null) {
                throw new LogicException("该机器不存在");
            }

            String roleCode = "1003";//运维人员

            //补货工单
            if (taskEntity.getProductTypeId() == VMSystem.TASK_TYPE_SUPPLY) {
                roleCode = "1002";//运营人员
            }

            String key = VMSystem.REGION_TASK_KEY_PREF
                    + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))   //日期
                    + "." + taskEntity.getRegionId()   //区域
                    + "." + roleCode;   //角色code

            redisTemplate.opsForZSet().incrementScore(key, taskEntity.getUserId(), score);

        }

        /**
         * 获取同一天内分配的工单最少的人
         * @param regionId 区域id
         * @param isRepair 是否是运维工单
         * @return
         */
        @Override
        public int getLeastUser (Long regionId, Boolean isRepair){

            String roleCode = "1003";//运维人员

            //补货工单
            if (!isRepair) {
                roleCode = "1002";//运营人员
            }

            String key = VMSystem.REGION_TASK_KEY_PREF
                    + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))   //日期
                    + "." + regionId //区域
                    + "." + roleCode;   //角色code

            Set<Object> range = redisTemplate.opsForZSet().range(key, 0, 0);
            if (range == null) {
                return 0;
            }

            return (Integer) range.stream().collect(Collectors.toList()).get(0);
        }
    }
